/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package EvoAlg;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
public class Algorithm {

    private int NumberOfPhenoTypes = 1;
    private int gene_length;
    private int popsize;
    private double pcrossover;
    private double pmutation;
    private GenericProblem problem;
    private Population pop;
    public static int TOURNAMENT_SELECT = 0;
    public static int FITNESS_PROP = 1;
    public static int SIGMA_SCALING = 2;
    public static int ALPHA_MALE = 3;
    public static int FACTOR = 100;
    public static double MIN_A = 0.001;
    public static double MAX_A = 0.2;
    public static double FACTOR_A = (MAX_A - MIN_A) / FACTOR;
    public static double MIN_B = 0.01;
    public static double MAX_B = 0.3;
    public static double FACTOR_B = (MAX_B - MIN_B) / FACTOR;
    public static double MIN_C = -80.0;
    public static double MAX_C = -30.0;
    public static double FACTOR_C = (MAX_C - MIN_C) / FACTOR;
    public static double MIN_D = 0.1;
    public static double MAX_D = 10.0;
    public static double FACTOR_D = (MAX_D - MIN_D) / FACTOR;
    public static double MIN_K = 0.01;
    public static double MAX_K = 1.0;
    public static double FACTOR_K = (MAX_K - MIN_K) / FACTOR;

    public Algorithm(GenericProblem p, int popsize,
            double pcrossover, double pmutation) {

        problem = p;
        this.popsize = popsize;
        this.NumberOfPhenoTypes = p.getNumberOfPhenoTypes();
        this.gene_length = p.getGenotypeLength();
        this.pcrossover = pcrossover;
        this.pmutation = pmutation;
        pop = new Population(popsize);


    }
    // BINARY TOURNAMENT

    public Individual select_tournament(Population pop) {
        int p1, p2;

        p1 = (int) (Math.random() * (double) popsize + 0.5);

        if (p1 > popsize - 1) {
            p1 = popsize - 1;
        }

        do {
            p2 = (int) (Math.random() * (double) popsize + 0.5);
            if (p2 > popsize - 1) {
                p2 = popsize - 1;
            }
        } while (p1 == p2);

        if (pop.getIndividualAtIndex(p1).getFitness() > pop.getIndividualAtIndex(p2).getFitness()) {
            return pop.getIndividualAtIndex(p1);
        } else {
            return pop.getIndividualAtIndex(p2);
        }
    }

    public Individual fitnesPropotionate(Population pop) {
        double fitnessTotal = 0.0;
        double fitnessTable[][] = new double[pop.getPopsize()][2];

        for (int i = 0; i < pop.getPopsize(); i++) {
            fitnessTotal += pop.getIndividualAtIndex(i).getFitness();
        }
        double lastStop = 0.0;

        for (int i = 0; i < pop.getPopsize(); i++) {
            double fraction = pop.getIndividualAtIndex(i).getFitness() / fitnessTotal;

            fitnessTable[i][0] = lastStop;
            fitnessTable[i][1] = lastStop + fraction;
            lastStop += fraction;
        }


        double chosen = Math.random();

        Individual target = new Individual();

        for (int i = 0; i < fitnessTable.length; i++) {
            if (chosen >= fitnessTable[i][0] && chosen <= fitnessTable[i][1]) {
                target = pop.getIndividualAtIndex(i);

                return target;
            }
        }
        return null;
    }

    public Individual sigmaScaling(Population pop) {
        double fitAvg = 0.0;
        double sigmaValues[] = new double[pop.getPopsize()];
        double sigmaTable[][] = new double[pop.getPopsize()][2];

        for (int i = 0; i < pop.getPopsize(); i++) {
            fitAvg += pop.getIndividualAtIndex(i).getFitness();
        }
        fitAvg /= pop.getPopsize();

        double sigmaSum = 0.0;
        for (int i = 0; i < sigmaValues.length; i++) {

            sigmaValues[i] = 1 + (pop.getIndividualAtIndex(i).getFitness() + fitAvg)
                    / (2 * standard_deviation(pop.getPop(), popsize));

            sigmaSum += sigmaValues[i];
        }
        double lastStop = 0.0;
        for (int i = 0; i < sigmaTable.length; i++) {
            double fraction = sigmaValues[i] / sigmaSum;

            sigmaTable[i][0] = lastStop;
            sigmaTable[i][1] = lastStop + fraction;
            lastStop += fraction;

        }

        double chosen = Math.random();

        Individual target = new Individual();

        for (int i = 0; i < sigmaTable.length; i++) {
            if (chosen >= sigmaTable[i][0] && chosen <= sigmaTable[i][1]) {
                target = pop.getIndividualAtIndex(i);
                //System.out.println(target.getFitness());
                return target;
            }
        }



        return null;
    }

    public Individual mutate(Individual p1) {
        double temp = 0;
        for (int i = 0; i < p1.getGene().getGenes().length; i++) {
            if (Math.random() < pmutation) {
                switch (i) {

                    case 1:
                        temp = p1.getGene().getGeneAtIndex(i).getWeight();
                        if (Math.random() < 0.5) {
                            if (temp + FACTOR_A > MAX_A) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MAX_A);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp + FACTOR_A);

                        } else {
                            if (temp + FACTOR_A < MIN_A) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MIN_A);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp - FACTOR_A);
                        }
                        break;

                    case 2:
                        temp = p1.getGene().getGeneAtIndex(i).getWeight();
                        if (Math.random() < 0.5) {
                            if (temp + FACTOR_B > MAX_B) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MAX_B);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp + FACTOR_B);

                        } else {
                            if (temp + FACTOR_B < MIN_B) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MIN_B);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp - FACTOR_B);
                        }
                        break;
                    case 3:
                        temp = p1.getGene().getGeneAtIndex(i).getWeight();
                        if (Math.random() < 0.5) {
                            if (temp + FACTOR_C > MAX_C) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MAX_C);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp + FACTOR_C);

                        } else {
                            if (temp + FACTOR_C < MIN_C) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MIN_C);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp - FACTOR_C);
                        }
                        break;
                    case 4:
                        temp = p1.getGene().getGeneAtIndex(i).getWeight();
                        if (Math.random() < 0.5) {
                            if (temp + FACTOR_D > MAX_D) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MAX_D);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp + FACTOR_D);

                        } else {
                            if (temp + FACTOR_D < MIN_D) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MIN_D);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp - FACTOR_D);
                        }
                        break;
                    case 5:
                        temp = p1.getGene().getGeneAtIndex(i).getWeight();
                        if (Math.random() < 0.5) {
                            if (temp + FACTOR_K > MAX_K) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MAX_K);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp + FACTOR_K);

                        } else {
                            if (temp + FACTOR_K < MIN_K) {
                                p1.getGene().getGeneAtIndex(i).setWeight(MIN_K);
                                break;
                            }
                            p1.getGene().getGeneAtIndex(i).setWeight(temp - FACTOR_K);
                        }
                        break;
                }
            }
        }
        return p1;
    }

    public Individual SinglePointCrossover(Individual p1, Individual p2) {


        int randIndex = (int) (Math.random() * 5);

        Individual ind = new Individual();
        if (Math.random() < pcrossover ) {
//        System.out.println(p1.getFitness());
            for (int i = 0; i < p1.getGene().getNumberOfGenes(); i++) {
                if (i < randIndex) {
                    ind.gene.setGeneAtIndex(p1.getGene().getGeneAtIndex(i), i);
                } else {
                    ind.gene.setGeneAtIndex(p2.getGene().getGeneAtIndex(i), i);
                }
            }
        }
        return ind;
    }

    public Individual createOffspring(int selection_mech) {
        Individual newborn = new Individual();
        if (selection_mech == TOURNAMENT_SELECT) {
            newborn = SinglePointCrossover(select_tournament(pop), select_tournament(pop));

        } else if (selection_mech == FITNESS_PROP) {
            Individual p1 = fitnesPropotionate(pop);
            Individual p2 = fitnesPropotionate(pop);
            newborn = SinglePointCrossover(p1, p2);

        } else if (selection_mech == SIGMA_SCALING) {
            Individual p1 = sigmaScaling(pop);
            Individual p2 = sigmaScaling(pop);
            newborn = SinglePointCrossover(p1, p2);

        } else if (selection_mech == ALPHA_MALE) {
            Individual p1 = findAlphaMale(pop);

            double rand = Math.random() * popsize - 1 + 0.5;
            int randI = (int) rand;

            Individual p2 = pop.getIndividualAtIndex(randI);
            newborn = SinglePointCrossover(p1, p2);
        }

        newborn = mutate(newborn);

        return newborn;
    }

    public double findMaxFitness() {
        double max = -400.0;
        for (int i = 0; i < pop.getPop().length; i++) {

            if (pop.getPop()[i].getFitness() > max) {
                max = pop.getPop()[i].getFitness();
            }

        }

        return max;
    }

    public Individual findAlphaMale(Population pop) {
        double max = -1000;
        Individual temp = null;
        for (int i = 0; i < pop.getPop().length; i++) {
            //System.out.println(pop.getPop()[i].getFitness());
            if (pop.getPop()[i].getFitness() >= max) {
                //System.out.println("MMMMMMMMM");
                max = pop.getPop()[i].getFitness();
                temp = pop.getPop()[i];
            }

        }

        return temp;
    }

    public void replace(Individual new_indiv) {
        pop.replaceWorst(new_indiv);
    }

    public int getNumberOfPhenoTypes() {
        return NumberOfPhenoTypes;
    }

    public void setNumberOfPhenoTypes(int NumberOfPhenoTypes) {
        this.NumberOfPhenoTypes = NumberOfPhenoTypes;
    }

    public int getGene_length() {
        return gene_length;
    }

    public void setGene_length(int gene_length) {
        this.gene_length = gene_length;
    }

    public double getPcrossover() {
        return pcrossover;
    }

    public void setPcrossover(double pcrossover) {
        this.pcrossover = pcrossover;
    }

    public double getPmutation() {
        return pmutation;
    }

    public void setPmutation(double pmutation) {
        this.pmutation = pmutation;
    }

    public Population getPop() {
        return pop;
    }

    public void setPop(Population pop) {
        this.pop = pop;
    }

    public int getPopsize() {
        return popsize;
    }

    public void setPopsize(int popsize) {
        this.popsize = popsize;
    }

    public GenericProblem getProblem() {
        return problem;
    }

    public void setProblem(GenericProblem problem) {
        this.problem = problem;
    }

    public double standard_deviation(Individual[] a, int n) {
        double average = 0;
        for (int i = 0; i < a.length; i++) {
            average = average + a[i].getFitness();
        }
        average /= n;

        double total = 0.0;

        for (int i = 0; i < a.length; i++) {
            total += Math.pow(a[i].getFitness() - average, 2);
        }
        return Math.sqrt(total);

    }

    public double avg() {

        double[] a = new double[pop.getPopsize()];
        for (int i = 0; i < a.length; i++) {
            a[i] = pop.getIndividualAtIndex(i).getFitness();

        }


        double average = 0;
        for (int i = 0; i < a.length; i++) {
            average = average + a[i];
        }
        average /= a.length;


        return average;

    }

    public double findMinFitness() {
        double min = 1.0;
        for (int i = 0; i < pop.getPop().length; i++) {

            if (pop.getPop()[i].getFitness() < min) {
                min = pop.getPop()[i].getFitness();
            }
        }

        return min;
    }
}
